﻿Imports System
Imports System.CodeDom
Imports System.ComponentModel.Design.Serialization
Imports System.Collections.Generic

Public Class NIB_CodeDomSerializer
   Inherits CodeDomSerializer
   Private ValueTypeProperties As New List(Of String)

   Private nibtype As Type = Type.GetType("TnTinMn.NIB.NumericInputBox, TnTinMn.NIB")

   Public Sub New()
      ValueTypeProperties.AddRange(New String() {"Value", "MaxValue", "MinValue"})
   End Sub

   Public Overrides Function Deserialize(ByVal manager As IDesignerSerializationManager, _
                                             ByVal codeObject As Object) As Object

      ' This is how we associate the component with the serializer. 
      Dim baseClassSerializer As CodeDomSerializer = CType(manager.GetSerializer( _
             nibtype.BaseType, GetType(CodeDomSerializer)), CodeDomSerializer)

      If TypeOf codeObject Is CodeStatementCollection Then

         Dim statementcollection As New CodeStatementCollection(CType(codeObject, CodeStatementCollection))
         'search for ValueType property assignment
         For Each statement As CodeStatement In statementcollection
            If TypeOf statement Is CodeAssignStatement Then

               Dim ca As CodeAssignStatement
               ca = CType(statement, CodeAssignStatement)

               If TypeOf ca.Left Is CodePropertyReferenceExpression AndAlso _
                  TypeOf ca.Right Is CodeCastExpression AndAlso _
                  ValueTypeProperties.Contains(CType(ca.Left, CodePropertyReferenceExpression).PropertyName) Then

                  'found the property assignment
                  'cast the right hand part to a CodeCastExpression

                  Dim cce As CodeCastExpression = CType(ca.Right, CodeCastExpression)
                  'Replace the cast, with the CodePrimitiveExpression
                  'that is being cast as it will type to cast to ValueType which will fail
                     ca.Right = cce.Expression


               End If 'TypeOf ca.Left Is CodePropertyReferenceExpression And _
                      'TypeOf ca.Right Is CodeCastExpression AndAlso _
                      'Properties.Contains(CType(ca.Left, CodePropertyReferenceExpression).PropertyName)
            End If 'TypeOf statement Is CodeAssignStatement
         Next 'statement
      End If 'TypeOf codeObject Is CodeStatementCollection

      ' call the base class Deserialize to do work
      Return baseClassSerializer.Deserialize(manager, codeObject)
   End Function 'Deserialize

   Public Overrides Function Serialize(ByVal manager As IDesignerSerializationManager, _
                                        ByVal value As Object) As Object

      Dim baseClassSerializer As CodeDomSerializer = CType(manager.GetSerializer( _
             nibtype.BaseType, GetType(CodeDomSerializer)), CodeDomSerializer)

      Dim codeObject As Object = baseClassSerializer.Serialize(manager, value)

      If TypeOf codeObject Is CodeStatementCollection Then

         Dim nib As System.Windows.Forms.Control = CType(manager.GetInstance(manager.GetName(value)), System.Windows.Forms.Control)

         Dim statements As CodeStatementCollection = CType(codeObject, CodeStatementCollection)

         ' need to determine the assigned systemtype
         Dim SystemType As Type = CType(Reflectionhelpers.GetPropNonPublic("SystemType", nib), Type)
         Dim statement As CodeStatement

         If nib IsNot Nothing Then

            '  Get my properties marked with ExpandablePropertyFlagAttribute

            Dim StatementsToAdd As New CodeStatementCollection
            Dim filter() As System.Attribute = {} ' dummy parameter to pass to SerializeProperties

            For Each prop As Reflection.PropertyInfo In nib.GetType.GetProperties()
               'If prop.GetCustomAttributes(GetType(ExpandablePropertyFlagAttribute), False).Length <> 0 Then
               If prop.GetCustomAttributes(Type.GetType("TnTinMn.NIB.ExpandablePropertyFlagAttribute", True, True), False).Length <> 0 Then

                  ' Get a code statement collection of for the property
                  Dim PropertyStatements As New CodeStatementCollection

                  SerializeProperties(manager, _
                                      PropertyStatements, _
                                      prop.GetValue(nib, Nothing), _
                                      filter)

                  StatementsToAdd.AddRange(ReFormattedPropertiesStatements(nib.Name, _
                                                                           prop.Name, _
                                                                           PropertyStatements) _
                                          )

                  ' remove to CodeAssignSatetment that assigns a new instance of this property to the NIB
                  Dim FullName As String = prop.GetValue(nib, Nothing).GetType.FullName

                  For i As Int32 = 0 To statements.Count - 1
                     If TypeOf statements(i) Is CodeAssignStatement Then
                        Dim cas As CodeAssignStatement = CType(statements(i), CodeAssignStatement)
                        If TypeOf cas.Right Is CodeObjectCreateExpression Then
                           Dim coce As CodeObjectCreateExpression = CType(cas.Right, CodeObjectCreateExpression)
                           If coce.CreateType.BaseType = FullName Then
                              statements.RemoveAt(i)
                              Exit For
                           End If 'coce.CreateType.BaseType = GetType(RangeCheckProperties).FullName
                        End If 'TypeOf cas.Right Is CodeObjectCreateExpression
                     End If 'TypeOf s Is CodeAssignStatement
                  Next i
               End If 'prop.GetCustomAttributes(GetType(ExpandablePropertyFlagAttribute), False).Length <> 0
            Next prop
            statements.AddRange(StatementsToAdd)
         End If 'nib IsNot Nothing 

         ' scan for selected properties that could be set either Type.MaxValue or Type.MinValue
         ' and use the Type defined constants for those.  Also ensures proper type setting since using
         ' ValueType as the base Type of the NumericInputBox
         ' Setting TypeMinValue or Type.MaxValue where needed prevents an error in the IDE that 
         ' can not handle the Int64MinValue that is entered by default.

         Dim MaxValue As ValueType
         Dim MinValue As ValueType
         If SystemType IsNot Nothing Then
            MaxValue = CType(SystemType.GetField("MaxValue", Reflection.BindingFlags.Static Or Reflection.BindingFlags.Public).GetValue(Nothing), ValueType)
            MinValue = CType(SystemType.GetField("MinValue", Reflection.BindingFlags.Static Or Reflection.BindingFlags.Public).GetValue(Nothing), ValueType)

            For k As Int32 = 0 To statements.Count - 1
               statement = statements(k)

               If TypeOf statement Is CodeAssignStatement Then
                  Dim ca As CodeAssignStatement
                  ca = CType(statement, CodeAssignStatement)

                  If TypeOf ca.Left Is CodePropertyReferenceExpression AndAlso _
                     ValueTypeProperties.Contains(CType(ca.Left, CodePropertyReferenceExpression).PropertyName) Then
                     'found the one of the properties
                     Dim propvalue As ValueType = Nothing

                     If TypeOf ca.Right Is CodePrimitiveExpression Then
                        propvalue = CType(CType(ca.Right, CodePrimitiveExpression).Value, ValueType)

                     ElseIf TypeOf ca.Right Is CodeObjectCreateExpression AndAlso SystemType Is GetType(Decimal) Then
                        Dim codeobj As CodeObjectCreateExpression = CType(ca.Right, CodeObjectCreateExpression)
                        Dim IntArray(0 To 3) As Int32
                        Dim intializers As CodeExpressionCollection = CType(codeobj.Parameters(0), CodeArrayCreateExpression).Initializers
                        For i As Int32 = 0 To 3
                           IntArray(i) = CInt(CType(intializers(i), CodePrimitiveExpression).Value)
                        Next
                        propvalue = New Decimal(IntArray)

                     ElseIf TypeOf ca.Right Is CodeCastExpression Then
                        Dim cce As CodeCastExpression = CType(ca.Right, CodeCastExpression)
                        'Replace the cast, with the CodePrimitiveExpression
                        'that is being cast

                        propvalue = CType(CType(cce.Expression, CodePrimitiveExpression).Value, ValueType)

                     End If 'TypeOf ca.Right Is CodePrimitiveExpression

                     If propvalue IsNot Nothing Then
                        If ValueType.Equals(propvalue, MaxValue) Then
                           ca.Right = New CodeSnippetExpression(SystemType.FullName & ".MaxValue")
                        ElseIf ValueType.Equals(propvalue, MinValue) Then
                           ca.Right = New CodeSnippetExpression(SystemType.FullName & ".MinValue")
                        End If 'ValueType.Equals(propvalue, MaxValue)
                     End If 'propvalue IsNot Nothing 

                  End If 'TypeOf ca.Left Is CodePropertyReferenceExpression AndAlso _
                         'Properties.Contains(CType(ca.Left, CodePropertyReferenceExpression).PropertyName)

               End If 'TypeOf statement Is CodeAssignStatement
            Next k

         End If 'SystemType IsNot Nothing
         ' End Scan for selected properties

         ' add a statement to call SuppressFormattingUpdates
         ' add it after the  3 comment lines

         'Dim suppress As New CodeSnippetStatement(nib.Name & ".SuppressFormattingUpdates()")
         Dim suppress As New CodeMethodInvokeExpression(New CodeThisReferenceExpression, _
                                                        nib.Name & ".SuppressFormattingUpdates", _
                                                        New CodeExpression() {})

         statements.Insert(4, New CodeExpressionStatement(suppress)) 'note would have thought index should be 3, but not

         ' add a statement to call ResumeFormattingUpdates
         Dim [resume] As New CodeMethodInvokeExpression(New CodeThisReferenceExpression, _
                                                      nib.Name & ".ResumeFormattingUpdates", _
                                                      New CodeExpression() {})

         statements.Add(New CodeExpressionStatement([resume]))


      End If 'TypeOf codeObject Is CodeStatementCollection


      Return codeObject
   End Function 'Serialize

   Private Function ReFormattedPropertiesStatements(ByVal NIBName As String, _
                                                    ByVal PropertyName As String, _
                                                    ByVal statements As CodeStatementCollection) _
                                                    As CodeStatementCollection
      ReFormattedPropertiesStatements = New CodeStatementCollection
      Dim propStatement As CodeAssignStatement

      For Each cs As CodeStatement In statements

         If TypeOf cs Is CodeAssignStatement AndAlso _
            TypeOf CType(cs, CodeAssignStatement).Left Is CodePropertyReferenceExpression Then

            ' got a property assignment statement

            propStatement = CType(cs, CodeAssignStatement)

            Dim currleft As CodePropertyReferenceExpression = CType(propStatement.Left, CodePropertyReferenceExpression)
            Dim base As New CodeFieldReferenceExpression
            base.TargetObject = New CodeThisReferenceExpression()
            base.FieldName = NIBName

            Dim RangeChecking As New CodePropertyReferenceExpression()
            RangeChecking.TargetObject = base
            RangeChecking.PropertyName = PropertyName

            Dim newLeft As New CodePropertyReferenceExpression
            newLeft.TargetObject = RangeChecking
            newLeft.PropertyName = currleft.PropertyName
            ReFormattedPropertiesStatements.Add(New CodeAssignStatement(newLeft, propStatement.Right))
         End If
            Next
   End Function
End Class
